package com.yu1996.gulimall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.yu1996.common.to.es.SkuEsModel;
import com.yu1996.common.utils.R;
import com.yu1996.gulimall.search.config.GulimallElasticSearchConfig;
import com.yu1996.gulimall.search.constant.EsConstant;
import com.yu1996.gulimall.search.service.MallSearchService;
import com.yu1996.gulimall.search.vo.SearchParam;
import com.yu1996.gulimall.search.vo.SearchResult;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

@Slf4j
@Service
public class MallSearchServiceImpl implements MallSearchService {

  @Autowired private RestHighLevelClient client;
  /**
   * 根据页面提交条件去es中检索商品
   *
   * @param param
   * @return
   */
  @Override
  public SearchResult search(SearchParam param) {
    // 1、动态构建出检所需要的DSL语句
    SearchResult result = null;
    // 1、准备检索请求
    SearchRequest searchRequest = bulidSearchRequest(param);
    try {
      SearchResponse response =
          client.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
      // 3、分析响应数据封装成我们需要的格式
      result = bulidSearchResult(param, response);
    } catch (IOException e) {
      e.printStackTrace();
    }
    return result;
  }

  /**
   * 构建结果数据
   *
   * @param searchParam
   * @param searchResponse
   * @return
   */
  private SearchResult bulidSearchResult(SearchParam searchParam, SearchResponse searchResponse) {

    SearchResult result = new SearchResult();
    SearchHits hits = searchResponse.getHits();
    // 1. 封装查询到的商品信息
    if (hits.getHits() != null && hits.getHits().length > 0) {
      List<SkuEsModel> skuEsModels = new ArrayList<>();
      for (SearchHit hit : hits) {
        String sourceAsString = hit.getSourceAsString();
        SkuEsModel skuEsModel = JSON.parseObject(sourceAsString, SkuEsModel.class);
        // 设置高亮属性
        if (!StringUtils.isEmpty(searchParam.getKeyword())) {
          HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
          String highLight = skuTitle.getFragments()[0].string();
          skuEsModel.setSkuTitle(highLight);
        }
        skuEsModels.add(skuEsModel);
      }
      result.setProducts(skuEsModels);
    }

    // 2. 封装分页信息
    // 2.1 当前页码
    result.setPageNum(searchParam.getPageNum());
    // 2.2 总记录数
    long total = hits.getTotalHits().value;
    result.setTotal(total);
    // 2.3 总页码
    Integer totalPages =
        (int) total % EsConstant.PRODUCT_PAGESIZE == 0
            ? (int) total / EsConstant.PRODUCT_PAGESIZE
            : (int) total / EsConstant.PRODUCT_PAGESIZE + 1;
    result.setTotalPages(totalPages);
    // 封装分页以便于前台遍历分页
    List<Integer> pageNavs = new ArrayList<>();
    for (int i = 1; i <= totalPages; i++) {
      pageNavs.add(i);
    }
    result.setPageNavs(pageNavs);

    // 3. 查询结果涉及到的品牌
    List<SearchResult.BrandVo> brandVos = new ArrayList<>();
    Aggregations aggregations = searchResponse.getAggregations();
    // ParsedLongTerms用于接收terms聚合的结果，并且可以把key转化为Long类型的数据
    ParsedLongTerms brandAgg = aggregations.get("brandAgg");
    for (Terms.Bucket bucket : brandAgg.getBuckets()) {
      // 3.1 得到品牌id
      Long brandId = bucket.getKeyAsNumber().longValue();

      Aggregations subBrandAggs = bucket.getAggregations();
      // 3.2 得到品牌图片
      ParsedStringTerms brandImgAgg = subBrandAggs.get("brandImgAgg");
      String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();
      // 3.3 得到品牌名字
      Terms brandNameAgg = subBrandAggs.get("brandNameAgg");
      String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
      SearchResult.BrandVo brandVo = new SearchResult.BrandVo(brandId, brandName, brandImg);
      brandVos.add(brandVo);
    }
    result.setBrands(brandVos);

    // 4. 查询涉及到的所有分类
    List<SearchResult.CatalogVo> catalogVos = new ArrayList<>();
    ParsedLongTerms catalogAgg = aggregations.get("catalogAgg");
    for (Terms.Bucket bucket : catalogAgg.getBuckets()) {
      // 4.1 获取分类id
      Long catalogId = bucket.getKeyAsNumber().longValue();
      Aggregations subcatalogAggs = bucket.getAggregations();
      // 4.2 获取分类名
      ParsedStringTerms catalogNameAgg = subcatalogAggs.get("catalogNameAgg");
      String catalogName = catalogNameAgg.getBuckets().get(0).getKeyAsString();
      SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo(catalogId, catalogName);
      catalogVos.add(catalogVo);
    }
    result.setCatalogs(catalogVos);

    // 5 查询涉及到的所有属性
    List<SearchResult.AttrVo> attrVos = new ArrayList<>();
    // ParsedNested用于接收内置属性的聚合
    ParsedNested parsedNested = aggregations.get("attrs");
    ParsedLongTerms attrIdAgg = parsedNested.getAggregations().get("attrIdAgg");
    for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {
      // 5.1 查询属性id
      Long attrId = bucket.getKeyAsNumber().longValue();

      Aggregations subAttrAgg = bucket.getAggregations();
      // 5.2 查询属性名
      ParsedStringTerms attrNameAgg = subAttrAgg.get("attrNameAgg");
      String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
      // 5.3 查询属性值
      ParsedStringTerms attrValueAgg = subAttrAgg.get("attrValueAgg");
      List<String> attrValues = new ArrayList<>();
      for (Terms.Bucket attrValueAggBucket : attrValueAgg.getBuckets()) {
        String attrValue = attrValueAggBucket.getKeyAsString();
        attrValues.add(attrValue);
        List<SearchResult.NavVo> navVos = new ArrayList<>();
      }
      SearchResult.AttrVo attrVo = new SearchResult.AttrVo(attrId, attrName, attrValues);
      attrVos.add(attrVo);
    }
    result.setAttrs(attrVos);

    //    // 6. 构建面包屑导航
    //    List<String> attrs = searchParam.getAttrs();
    //    if (attrs != null && attrs.size() > 0) {
    //      List<SearchResult.NavVo> navVos =
    //          attrs.stream()
    //              .map(
    //                  attr -> {
    //                    String[] split = attr.split("_");
    //                    SearchResult.NavVo navVo = new SearchResult.NavVo();
    //                    // 6.1 设置属性值
    //                    navVo.setNavValue(split[1]);
    //                    // 6.2 查询并设置属性名
    //                    try {
    //                      R r = productFeignService.info(Long.parseLong(split[0]));
    //                      if (r.getCode() == 0) {
    //                        AttrResponseVo attrResponseVo =
    //                            JSON.parseObject(
    //                                JSON.toJSONString(r.get("attr")),
    //                                new TypeReference<AttrResponseVo>() {});
    //                        navVo.setNavName(attrResponseVo.getAttrName());
    //                      }
    //                    } catch (Exception e) {
    //                      log.error("远程调用商品服务查询属性失败", e);
    //                    }
    //                    // 6.3 设置面包屑跳转链接
    //                    String queryString = searchParam.get_queryString();
    //                    String replace =
    //                        queryString
    //                            .replace("&attrs=" + attr, "")
    //                            .replace("attrs=" + attr + "&", "")
    //                            .replace("attrs=" + attr, "");
    //                    navVo.setLink(
    //                        "http://search.gulimall.com/search.html"
    //                            + (replace.isEmpty() ? "" : "?" + replace));
    //                    return navVo;
    //                  })
    //              .collect(Collectors.toList());
    //      result.setNavs(navVos);
    //    }
    return result;
  }

  /**
   * 准备检索请求
   *
   * @param searchParam
   * @return
   */
  private SearchRequest bulidSearchRequest(SearchParam searchParam) {

    // 用于构建DSL语句
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    // 1. 构建bool query
    BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
    // 1.1 bool must
    if (!StringUtils.isEmpty(searchParam.getKeyword())) {
      boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle", searchParam.getKeyword()));
    }
    // 1.2 bool filter
    // 1.2.1 catalog
    if (searchParam.getCatalog3Id() != null) {
      boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId", searchParam.getCatalog3Id()));
    }
    // 1.2.2 brand
    if (searchParam.getBrandId() != null && searchParam.getBrandId().size() > 0) {
      boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId", searchParam.getBrandId()));
    }
    // 1.2.3 hasStock
    if (searchParam.getHasStock() != null) {
      boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock", searchParam.getHasStock() == 1));
    }
    // 1.2.4 priceRange
    RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("skuPrice");
    if (!StringUtils.isEmpty(searchParam.getSkuPrice())) {
      String[] prices = searchParam.getSkuPrice().split("_");
      if (prices.length == 1) {
        if (searchParam.getSkuPrice().startsWith("_")) {
          rangeQueryBuilder.lte(Integer.parseInt(prices[0]));
        } else {
          rangeQueryBuilder.gte(Integer.parseInt(prices[0]));
        }
      } else if (prices.length == 2) {
        // _6000会截取成["","6000"]
        if (!prices[0].isEmpty()) {
          rangeQueryBuilder.gte(Integer.parseInt(prices[0]));
        }
        rangeQueryBuilder.lte(Integer.parseInt(prices[1]));
      }
      boolQueryBuilder.filter(rangeQueryBuilder);
    }
    // 1.2.5 attrs-nested
    // attrs=1_5寸:8寸&2_16G:8G
    List<String> attrs = searchParam.getAttrs();

    if (attrs != null && attrs.size() > 0) {
      attrs.forEach(
          attr -> {
            BoolQueryBuilder nestedBoolQueryBuilder = new BoolQueryBuilder();
            String[] attrSplit = attr.split("_");
            nestedBoolQueryBuilder.must(QueryBuilders.termQuery("attrs.attrId", attrSplit[0]));
            String[] attrValues = attrSplit[1].split(":");
            nestedBoolQueryBuilder.must(QueryBuilders.termsQuery("attrs.attrValue", attrValues));
            // 每一个都必须生成一个nested查询
            NestedQueryBuilder nestedQueryBuilder =
                QueryBuilders.nestedQuery("attrs", nestedBoolQueryBuilder, ScoreMode.None);
            boolQueryBuilder.filter(nestedQueryBuilder);
          });
    }
    // 1.X bool query构建完成
    searchSourceBuilder.query(boolQueryBuilder);

    // 2. sort  eg:sort=saleCount_desc/asc
    if (!StringUtils.isEmpty(searchParam.getSort())) {
      String[] sortSplit = searchParam.getSort().split("_");
      searchSourceBuilder.sort(
          sortSplit[0], sortSplit[1].equalsIgnoreCase("asc") ? SortOrder.ASC : SortOrder.DESC);
    }

    // 3. 分页 // 是检测结果分页
    searchSourceBuilder.from((searchParam.getPageNum() - 1) * EsConstant.PRODUCT_PAGESIZE);
    searchSourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);

    // 4. 高亮highlight
    if (!StringUtils.isEmpty(searchParam.getKeyword())) {
      HighlightBuilder highlightBuilder = new HighlightBuilder();
      highlightBuilder.field("skuTitle");
      highlightBuilder.preTags("<b style='color:red'>");
      highlightBuilder.postTags("</b>");
      searchSourceBuilder.highlighter(highlightBuilder);
    }

    // 5. 聚合
    // 5.1 按照brand聚合
    TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brandAgg").field("brandId");
    TermsAggregationBuilder brandNameAgg =
        AggregationBuilders.terms("brandNameAgg").field("brandName");
    TermsAggregationBuilder brandImgAgg =
        AggregationBuilders.terms("brandImgAgg").field("brandImg");
    brandAgg.subAggregation(brandNameAgg);
    brandAgg.subAggregation(brandImgAgg);
    searchSourceBuilder.aggregation(brandAgg);

    // 5.2 按照catalog聚合
    TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catalogAgg").field("catalogId");
    // 子聚合
    TermsAggregationBuilder catalogNameAgg =
        AggregationBuilders.terms("catalogNameAgg").field("catalogName");
    catalogAgg.subAggregation(catalogNameAgg);
    searchSourceBuilder.aggregation(catalogAgg);

    // 5.3 按照attrs聚合
    NestedAggregationBuilder nestedAggregationBuilder =
        new NestedAggregationBuilder("attrs", "attrs");
    // 按照attrId聚合     //按照attrId聚合之后再按照attrName和attrValue聚合
    TermsAggregationBuilder attrIdAgg =
        AggregationBuilders.terms("attrIdAgg").field("attrs.attrId");
    TermsAggregationBuilder attrNameAgg =
        AggregationBuilders.terms("attrNameAgg").field("attrs.attrName");
    TermsAggregationBuilder attrValueAgg =
        AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue");
    attrIdAgg.subAggregation(attrNameAgg);
    attrIdAgg.subAggregation(attrValueAgg);

    nestedAggregationBuilder.subAggregation(attrIdAgg);
    searchSourceBuilder.aggregation(nestedAggregationBuilder);

    log.debug("构建的DSL语句 {}", searchSourceBuilder.toString());

    SearchRequest request =
        new SearchRequest(new String[] {EsConstant.PRODUCT_INDEX}, searchSourceBuilder);
    return request;
  }
}
